﻿// 
//    Copyright 2010 The JaernCloud Project
// 
//    Licensed under the Apache License, Version 2.0 (the "License");
//    you may not use this file except in compliance with the License.
//    You may obtain a copy of the License at
// 
//        http://www.apache.org/licenses/LICENSE-2.0
// 
//    Unless required by applicable law or agreed to in writing, software
//    distributed under the License is distributed on an "AS IS" BASIS,
//    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//    See the License for the specific language governing permissions and
//    limitations under the License.
// 
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Dynamic;
using JaernCloud.Language.Compiler.Types;
using JaernCloud.Language.Compiler.Types.Builtin.Model;
using System.Linq.Expressions;

namespace JaernCloud.Language.Runtime.Model {
    
    /// <summary>
    /// Integer node object
    /// </summary>
    public class IntegerNodeObject : DynamicObject, INodeObjectValue<int, IntegerNodeType>, INodeObjectValidator {
        
        // TODO: Enforce validation rules based on the IntegerNodeType (min, max etc.)

        readonly int _id = NodeObjectExtensions.NewId;
        public int Id { get { return _id; } }

        public INodeObject<IntegerNodeType> FromType(INodeType nodeType, INodeObjectGenerator nodeObjectGenerator) {
            return new IntegerNodeObject { _type = (IntegerNodeType)nodeType, _nodeObjectGenerator = _nodeObjectGenerator };
        }

        public INodeObjectValueHolder ValueHolder { get; set; }

        #region DynamicObject

        public override bool TryConvert(System.Dynamic.ConvertBinder binder, out object result) {
            if (binder.Type == typeof(int)) {
                result = Value;
                return true;
            }
            if (binder.Type == typeof(string)) {
                result = Convert.ToString(Value);
                return true;
            }
            return base.TryConvert(binder, out result);
        }

        public override bool TrySetMember(SetMemberBinder binder, object value) {
            if (value is int) {
                Value = (int)value;
                return true;
            }
            if (value == this) {
                // reassign to the same node, eg. for node++
                return true;
            }
            return base.TrySetMember(binder, value);
        }

        public override bool TryUnaryOperation(UnaryOperationBinder binder, out object result) {
            // TODO: Increment/Decrement should not modify, see http://dlr.codeplex.com/Thread/View.aspx?ThreadId=229000
            switch (binder.Operation) {
                case ExpressionType.Increment:
                case ExpressionType.PostIncrementAssign:
                    _value++;
                    result = this;
                    return true;
                case ExpressionType.Decrement:
                case ExpressionType.PostDecrementAssign:
                    _value--;
                    result = this;
                    return true;
            }
            return base.TryUnaryOperation(binder, out result);
        }

        public override bool TryBinaryOperation(BinaryOperationBinder binder, object arg, out object result) {
            return base.TryBinaryOperation(binder, arg, out result);
        }

        #endregion


        private int _value;
        private IntegerNodeType _type;
        private INodeObject<ModelNodeType> _parent;
        private INodeObjectGenerator _nodeObjectGenerator;

        public int Value {
            get {
                return _value;
            }
            set {
                _value = value;
            }
        }

        public IntegerNodeType Type {
            get { return _type; }
        }

        public IEnumerable<INodeObject<INodeType>> Children {
            get { yield break; }
        }

        public INodeObject<INodeType> Parent {
            get {
                return _parent;
            }
            set {
                if (value is INodeObject<ModelNodeType>) {
                    _parent = value as INodeObject<ModelNodeType>;
                } else {
                    throw new ArgumentException("Parent type must be " + typeof(ModelNodeType));
                }
            }
        }

        public string Name {
            get { return _type.Name; }
        }

        public IEnumerable<string> Names {
            get { return _type.Names; }
        }

        public IEnumerable<object> Values {
            get { yield return _value; }
        }

        public override string ToString() {
            return "["+Id+"] " + this.GetType().Name + "(" + _type.ToString() + ")";
        }

    }
}
